#pragma once
#include <math.h>
#include <eosiolib/asset.hpp>
#include <eosiolib/time.hpp>
#include <eosiolib/eosio.hpp>
#include <eosio.token/eosio.token.hpp>
#include "random.hpp"
#include "config.hpp"

namespace eosio {
    //赛车冲刺游戏
    class car_sprint_game {
        private:
            uint32_t gameMinutes = 60;          //多长时间一局游戏
            uint8_t  stopBetTime = 50;          //每局游戏的第几秒停止下注
            uint64_t cardinal    = 100000000;   //游戏ID基数，算法 游戏ID = (游戏类型 * 基数) + 游戏场次编号
            uint64_t game3Amount = 1 * 10000;   //3赛道游戏下注需要EOS数量
            uint64_t game5Amount = 3 * 10000;   //5赛道游戏下注需要EOS数量
            uint64_t game7Amount = 5 * 10000;   //7赛道游戏下注需要EOS数量
            /// @abi table carinfo i64
            struct carinfo {
                uint64_t id;
                uint64_t creation;          //第一局游戏创建时间
                uint64_t primary_key() const { return id; }
                EOSLIB_SERIALIZE( carinfo, (id)(creation) )
            };

            typedef eosio::multi_index<N(carinfo), carinfo> carinfos;
            carinfos carinfo_table;

            /// @abi table cargame i64
            struct cargame {
                uint64_t id;
                uint16_t game;              //游戏场次 3-5-7
                uint8_t  number;            //中奖号码
                asset    pool;              //奖池
                uint16_t peoples;           //下注人数
                uint16_t confirm;           //下注确认合法人数
                uint64_t createtime;        //游戏创建时间
                uint64_t primary_key() const { return id; }
                uint64_t get_game() const { return game; }
                uint64_t get_createtime() const { return createtime; }
                EOSLIB_SERIALIZE( cargame, (id)(game)(number)(pool)(peoples)(confirm)(createtime) )
            };

            typedef eosio::multi_index<N(cargame), cargame,
                    indexed_by< N(bygame), const_mem_fun<cargame, uint64_t, &cargame::get_game> >,
                    indexed_by< N(by*), const_mem_fun<cargame, uint64_t, &cargame::get_createtime> >
                    > cargames;

            cargames cargame_table;

            /// @abi table carbet i64
            struct carbet {
                uint64_t     id;
                uint64_t     gameid;          //游戏id
                uint64_t     userid;          //下注玩家用户ID
                account_name user;            //下注玩家名称
                uint8_t      digital;         //下注的数字
                asset        quantity;        //下注EOS金额
                asset        reward;          //奖励EOS金额
                uint64_t     confirm;         //是否确认        0：未处理  1：已处理   2：异常交易
                uint64_t     bettime;         //下注时间
                uint64_t     primary_key() const { return id; }
                uint64_t     get_userid() const { return userid; }
                uint64_t     get_gameid() const { return gameid; }
                uint64_t     get_confirm() const { return confirm; }
                EOSLIB_SERIALIZE( carbet, (id)(gameid)(userid)(user)(digital)(quantity)(reward)(confirm)(bettime) )
            };

            typedef eosio::multi_index<N(carbet), carbet,
                  indexed_by< N(byuserid), const_mem_fun<carbet, uint64_t, &carbet::get_userid> >,
                  indexed_by< N(bygameid), const_mem_fun<carbet, uint64_t, &carbet::get_gameid> >,
                  indexed_by< N(byconfirm), const_mem_fun<carbet, uint64_t, &carbet::get_confirm> >
                  > carbets;

            carbets carbet_table;

        public:
            account_name _self;
            random *rand;
            car_sprint_game(account_name self):cargame_table(self, self), carbet_table(self, self), carinfo_table(self, self) {
                _self = self;
                rand = new random( _self );
            }

            //获取当前合法的游戏ID和创建时间
            uint64_t* getNum( uint64_t game ) {
                auto itr = carinfo_table.begin();
                uint64_t createtime = now();
                uint64_t nowtime  = now();              //当前时间
                uint64_t num = 1;
                uint64_t gameid = 0;
                if( itr == carinfo_table.end() ) {
                    carinfo_table.emplace( _self, [&](auto &result ) {
                        result.id       = 1;
                        result.creation = createtime;
                    });
                }
                else {
                    uint64_t creation = itr->creation;      //获取第一局游戏创建时间
                    num = ceil((nowtime - creation) / gameMinutes) + 1;
                    createtime = creation + (gameMinutes * (num - 1));
                }

                uint64_t* info = new uint64_t[3];
                info[0] = (game * cardinal) + num;
                info[1] = createtime;
                info[2] = nowtime - createtime;
                return info;
            }

            //根据下注金额判断游戏类型
            uint32_t getGame( asset quantity ) {
                uint32_t game = 0;
				if ( quantity.amount == game3Amount ) {
					game = 3;
				}
				else if ( quantity.amount == game5Amount ) {
					game = 5;
				}
				else if ( quantity.amount == game7Amount ) {
					game = 7;
				}

                return game;
            }

            void createGame( uint64_t game, uint64_t gameid, uint64_t createtime, uint64_t userid ) {
                auto gameitr = cargame_table.find( gameid );
                uint32_t number = rand->range( game, gameid, userid );
                if( gameitr ==  cargame_table.end() ) {
                    cargame_table.emplace( _self, [&](auto &result ) {
                        result.id         = gameid;
                        result.game       = game;
                        result.number     = number;
                        result.createtime = createtime;
                    });
                }
                else {
                    cargame_table.modify( gameitr, _self, [&](auto &game_result ) {
                        game_result.number = number;
                    });
                }
            }

            //获取游戏结果，并结算游戏奖励
            vector<config::Settlement> settleGame( uint64_t gameid ) {
                vector<config::Settlement> settle;
                auto gameitr = cargame_table.find( gameid );
                eosio_assert( gameitr != cargame_table.end(), "110" );

                //计算获奖数据
                auto luckbet_index = carbet_table.get_index<N(bygameid)>();
                auto betitr = luckbet_index.find( gameid );

                //计算总的可分配的奖励金额 85%
                uint64_t total_bonus = ceil( (gameitr->pool.amount / 100) * 85 );
                vector<config::BetInfo> guess;
                vector<config::BetInfo> wrong;
                
                extended_asset zeroEos( 0, S(4,EOS) );
                while(betitr != luckbet_index.end()) {
                    uint64_t userid  = betitr->userid;
                    uint8_t  digital = betitr->digital;
                    struct config::BetInfo btinfo;
                    btinfo.userid = userid;
                    btinfo.digital = digital;
                    if (betitr->digital == gameitr->number) {
                        guess.push_back( btinfo );
                    }
                    else {
                        wrong.push_back( btinfo );
                    }

                    luckbet_index.modify( betitr, _self, [&](auto &result ) {
                        result.reward = zeroEos;
                    });

                    betitr++;
                }

                //没有一个玩家抽中号码，或者所有玩家都抽中号码
                uint32_t count;
                if ( guess.empty() ) {
                    //都没有抽中
                    count = wrong.size();
                }
                else if (wrong.empty()) {
                    //都抽中
                    count = guess.size();
                }
                else {
                    //部分抽中
                    count = guess.size();
                }

                for ( uint32_t i = 0; i < count; i++ )
                {
                    uint32_t bonus  = ceil(total_bonus / count);
                    uint64_t userid = wrong[i].userid;
                    uint8_t  digital = 0;
                    if ( guess.empty() ) {
                        //都没有抽中
                        userid = wrong[i].userid;
                        digital = wrong[i].digital;
                    }
                    else if (wrong.empty()) {
                        //都抽中
                        userid = guess[i].userid;
                        digital = guess[i].digital;
                    }
                    else {
                        //部分抽中
                        userid = guess[i].userid;
                        digital = guess[i].digital;
                    }
                    struct config::Settlement sett;
                        
                    sett.userid   = userid;
                    sett.bonus    = bonus;
                    settle.push_back( sett );

                    extended_asset bonusEos( bonus,S(4,EOS) );
                    saveAwardInfo( gameid, userid, bonusEos, digital );
                }

                guess.clear();
                wrong.clear();
                return settle;
            }

            //保存发奖信息
            void saveAwardInfo( uint64_t gameid, uint64_t userid, asset quantity, uint8_t digital ) {
                auto luckbet_index = carbet_table.get_index<N(bygameid)>();
                auto betitr = luckbet_index.find( gameid );
                uint64_t betid;
                while( betitr != luckbet_index.end() ) {
                    if ( betitr->userid == userid and betitr->digital == digital ) {
                        betid = betitr->id;
                        luckbet_index.modify( betitr, _self, [&](auto &result ) {
                            result.reward = quantity;
                        });
                        break;
                    }
                    betitr++;
                }
            }

            //设置交易状态，如果成功则获取要发放奖励的金额，如果失败则设置状态
            asset transfer( uint64_t gameid, uint64_t userid, uint8_t digital ) {
                auto gameitr = cargame_table.find( gameid );
                auto luckbet_index = carbet_table.get_index<N(bygameid)>();
                auto betitr = luckbet_index.find( gameid );
                eosio_assert( now() - gameitr->createtime > gameMinutes, "116" );
                uint64_t rewardAmount = 0;
                while( betitr != luckbet_index.end() ) {
                    if( betitr->userid == userid and betitr->digital == digital and betitr->confirm == 0 ) {
                        luckbet_index.modify( betitr, _self, [&](auto &result ) {
                            result.confirm = 1;
                        });

                        cargame_table.modify( gameitr, _self, [&](auto &result ) {
                            result.confirm = gameitr->confirm + 1;
                        });
                        rewardAmount = betitr->reward.amount;
                        break;
                    }
                    betitr++;
                }
                extended_asset rewardEos( rewardAmount, S(4,EOS) );
                return rewardEos;
            }

            //游戏下注
            void bet( uint64_t gameid, uint64_t game, uint64_t createtime, uint64_t userid, account_name user, asset quantity, uint8_t digital ) {
                eosio_assert( quantity.symbol == S(4,EOS) , "111" ); 
                eosio_assert( quantity.amount > 0, "112" );
                eosio_assert( digital <= game, "114" );
                uint64_t nowtime = now();
                createGame( game, gameid, createtime, userid );

                //游戏在最后N秒后停止下注
                eosio_assert( (nowtime - createtime) <= stopBetTime, "113" );

                auto luckbet_index = carbet_table.get_index<N(bygameid)>();
                auto finditr = luckbet_index.find( gameid );
                bool findUserIdAndDigital = false;
                while( finditr != luckbet_index.end() ) {
                    if ( finditr->userid == userid and finditr->digital == digital ) {
                        findUserIdAndDigital = true;
                        break;
                    }
                    finditr++;
                }

                eosio_assert( findUserIdAndDigital == false, "115" );
                carbet_table.emplace( _self, [&](auto &result ) {
                    result.id       = carbet_table.available_primary_key();
                    result.gameid   = gameid;
                    result.userid   = userid;
                    result.user     = user;
                    result.digital  = digital;
                    result.quantity = quantity;
                    result.bettime  = nowtime;

                    if (result.id == 0) {
                        result.id = 1;
                    }
                });

                betSettlement( gameid, userid, quantity, digital );
            }

            //修改奖池数据，并累加下注人数信息
            void betSettlement( uint64_t gameid, uint64_t userid, asset quantity, uint32_t digital ) {
                auto gameitr = cargame_table.find( gameid );
                eosio_assert( gameitr != cargame_table.end() , "110" );
                uint32_t game = gameitr->game;
                //设置奖池
                asset pool = gameitr->pool;
                extended_asset newpool( pool.amount + quantity.amount, S(4,EOS) );
                
                cargame_table.modify( gameitr, _self, [&](auto &result ) {
                    result.pool    = newpool;
                    result.peoples = gameitr->peoples + 1;
                });

                settleGame( gameid );
            }
    };
}